package com.atom.module.lib.opengles.code

import android.opengl.GLES30
import com.atom.module.lib.opengles.util.ShaderUtils
import java.nio.Buffer
import java.nio.FloatBuffer
import java.nio.IntBuffer

enum class GLType(val tag: String) {

    Attribute("attribute"), Uniform("uniform"), Varying("varying");

    override fun toString(): String {
        return tag
    }
}

enum class GLClass(val type: String) {
    Bool("bool"), Float("float"), Int("int"), UInt("uint"),

    Vec2("vec2"), Vec3("vec3"), Vec4("vec4"),
    BVec2("bvec2"), BVec3("bvec3"), BVec4("bvec4"),
    IVec2("ivec2"), IVec3("ivec3"), IVec4("ivec4"),
    UIVec2("uvec2"), UIVec3("uvec3"), UIVec4("uvec4"),

    Mat2x2("mat2x2"), Mat2x3("mat2x3"), Mat2x4("mat2x4"),
    Mat3x2("mat3x2"), Mat3x3("mat3x3"), Mat3x4("mat3x4"),
    Mat4x2("mat4x2"), Mat4x3("mat4x3"), Mat4x4("mat4x4"),
    Mat2("mat2"), Mat3("mat3"), Mat4("mat4"),

    SamplerExternalOES("samplerExternalOES"),

    Sampler2D(" sampler2D"), Sampler3D("sampler3D"),
    SamplerCube(" samplerCube"), SamplerCubeShadow(" samplerCubeShadow"),
    Sampler2DShadow(" sampler2DShadow"), Sampler2DArray(" sampler2DArray"),
    Sampler2DArrayShadow(" sampler2DArrayShadow"),

    ISampler2D("isampler2D"), ISampler3D("isampler3D"),
    ISamplerCube("isamplerCube"), ISampler2DArray("isampler2DArray"),

    UISampler2D("usampler2D"), UISampler3D("usampler3D"),
    UISamplerCube("usamplerCube"), UISampler2DArray("usampler2DArray");

    override fun toString(): String {
        return this.type
    }
}

class GLObject(
    val type: GLClass,
    val name: String
) {
    override fun toString(): String {
        return "$type $name"
    }
}

fun GLClass.create(name: String): GLObject {
    return GLObject(this, name)
}

abstract class GLVariable(val obj: GLObject, private var handle: Int = -1) {

    abstract fun type(): GLType

    override fun toString(): String {
        return "${type()} $obj"
    }

    fun name() = obj.name

    fun handle(program: Int): Int {
        if (handle != -1) return handle
        return try {
            when (type()) {
                GLType.Attribute -> GLES30.glGetAttribLocation(program, name())
                GLType.Uniform -> GLES30.glGetUniformLocation(program, name())
                GLType.Varying -> -1
            }.also {
                if (handle == -1) {
                    handle = it
                }
            }
        } catch (e: Exception) {
            ShaderUtils.checkGLError("program[${program} , name[$obj]")
            throw e
        }
    }
}

class GLUniformVariable(obj: GLObject) : GLVariable(obj) {
    override fun type(): GLType {
        return GLType.Uniform
    }

    fun glUniform1f(
        program: Int,
        x: Float
    ) {
        GLES30.glUniform1f(this.handle(program), x)
    }

    fun glUniform1fv(
        program: Int,
        count: Int,
        v: FloatArray,
        offset: Int
    ) {
        GLES30.glUniform1fv(this.handle(program), count, v, offset)
    }

    fun glUniform1fv(
        program: Int,
        count: Int,
        v: FloatBuffer
    ) {
        GLES30.glUniform1fv(this.handle(program), count, v)
    }

    fun glUniform1i(
        program: Int,
        x: Int
    ) {
        GLES30.glUniform1i(this.handle(program), x)
    }

    fun glUniform1iv(
        program: Int,
        count: Int,
        v: IntArray?,
        offset: Int
    ) {
        GLES30.glUniform1iv(this.handle(program), count, v, offset)
    }

    fun glUniform1iv(
        program: Int,
        count: Int,
        v: IntBuffer?
    ) {
        GLES30.glUniform1iv(this.handle(program), count, v)
    }

    fun glUniform2f(
        program: Int,
        x: Float,
        y: Float
    ) {
        GLES30.glUniform2f(this.handle(program), x, y)
    }

    fun glUniform2fv(
        program: Int,
        count: Int,
        v: FloatArray,
        offset: Int
    ) {
        GLES30.glUniform2fv(this.handle(program), count, v, offset)
    }

    fun glUniform2fv(
        program: Int,
        count: Int,
        v: FloatBuffer
    ) {
        GLES30.glUniform2fv(this.handle(program), count, v)
    }

    fun glUniform2i(
        program: Int,
        x: Int,
        y: Int
    ) {
        GLES30.glUniform2i(this.handle(program), x, y)
    }

    fun glUniform2iv(
        program: Int,
        count: Int,
        v: IntArray?,
        offset: Int
    ) {
        GLES30.glUniform2iv(this.handle(program), count, v, offset)
    }

    fun glUniform2iv(
        program: Int,
        count: Int,
        v: IntBuffer?
    ) {
        GLES30.glUniform2iv(this.handle(program), count, v)
    }

    fun glUniform3f(
        program: Int,
        x: Float,
        y: Float,
        z: Float
    ) {
        GLES30.glUniform3f(this.handle(program), x, y, z)
    }

    fun glUniform3fv(
        program: Int,
        count: Int,
        v: FloatArray,
        offset: Int
    ) {
        GLES30.glUniform3fv(this.handle(program), count, v, offset)
    }

    fun glUniform3fv(
        program: Int,
        count: Int,
        v: FloatBuffer
    ) {
        GLES30.glUniform3fv(this.handle(program), count, v)
    }

    fun glUniform3i(
        program: Int,
        x: Int,
        y: Int,
        z: Int
    ) {
        GLES30.glUniform3i(this.handle(program), x, y, z)
    }

    fun glUniform3iv(
        program: Int,
        count: Int,
        v: IntArray?,
        offset: Int
    ) {
        GLES30.glUniform3iv(this.handle(program), count, v, offset)
    }

    fun glUniform3iv(
        program: Int,
        count: Int,
        v: IntBuffer?
    ) {
        GLES30.glUniform3iv(this.handle(program), count, v)
    }

    fun glUniform4f(
        program: Int,
        x: Float,
        y: Float,
        z: Float,
        w: Float
    ) {
        GLES30.glUniform4f(this.handle(program), x, y, z, w)
    }

    fun glUniform4fv(
        program: Int,
        count: Int,
        v: FloatArray,
        offset: Int
    ) {
        GLES30.glUniform4fv(this.handle(program), count, v, offset)
    }

    fun glUniform4fv(
        program: Int,
        count: Int,
        v: FloatBuffer
    ) {
        GLES30.glUniform4fv(this.handle(program), count, v)
    }

    fun glUniform4i(
        program: Int,
        x: Int,
        y: Int,
        z: Int,
        w: Int
    ) {
        GLES30.glUniform4i(this.handle(program), x, y, z, w)
    }

    fun glUniform4iv(
        program: Int,
        count: Int,
        v: IntArray?,
        offset: Int
    ) {
        GLES30.glUniform4iv(this.handle(program), count, v, offset)
    }

    fun glUniform4iv(
        program: Int,
        count: Int,
        v: IntBuffer?
    ) {
        GLES30.glUniform4iv(this.handle(program), count, v)
    }

    fun glUniformMatrix2fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatArray,
        offset: Int
    ) {
        GLES30.glUniformMatrix2fv(this.handle(program), count, transpose, value, offset)
    }

    fun glUniformMatrix2fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatBuffer
    ) {
        GLES30.glUniformMatrix2fv(this.handle(program), count, transpose, value)
    }

    fun glUniformMatrix3fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatArray,
        offset: Int
    ) {
        GLES30.glUniformMatrix3fv(this.handle(program), count, transpose, value, offset)
    }

    fun glUniformMatrix3fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatBuffer
    ) {
        GLES30.glUniformMatrix3fv(this.handle(program), count, transpose, value)
    }

    fun glUniformMatrix4fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatArray,
        offset: Int
    ) {
        GLES30.glUniformMatrix4fv(this.handle(program), count, transpose, value, offset)
    }

    fun glUniformMatrix4fv(
        program: Int,
        count: Int,
        transpose: Boolean,
        value: FloatBuffer
    ) {
        GLES30.glUniformMatrix4fv(this.handle(program), count, transpose, value)
    }


}

class GLAttributeVariable(obj: GLObject) : GLVariable(obj) {
    override fun type(): GLType {
        return GLType.Attribute
    }

    fun glDisableVertexAttribArray(
        program: Int
    ) {
        GLES30.glDisableVertexAttribArray(this.handle(program))
    }

    fun glEnableVertexAttribArray(
        program: Int
    ) {
        GLES30.glEnableVertexAttribArray(this.handle(program))
    }

    fun glGetVertexAttribfv(
        program: Int,
        pname: Int,
        params: FloatArray,
        offset: Int
    ) {
        GLES30.glGetVertexAttribfv(this.handle(program), pname, params, offset)
    }

    fun glGetVertexAttribfv(
        program: Int,
        pname: Int,
        params: FloatBuffer
    ) {
        GLES30.glGetVertexAttribfv(this.handle(program), pname, params)
    }

    fun glGetVertexAttribiv(
        program: Int,
        pname: Int,
        params: IntArray?,
        offset: Int
    ) {
        GLES30.glGetVertexAttribiv(this.handle(program), pname, params, offset)
    }

    fun glGetVertexAttribiv(
        program: Int,
        pname: Int,
        params: IntBuffer?
    ) {
        GLES30.glGetVertexAttribiv(this.handle(program), pname, params)
    }

    fun glVertexAttrib1f(
        program: Int,
        x: Float
    ) {
        GLES30.glVertexAttrib1f(this.handle(program), x)
    }

    fun glVertexAttrib1fv(
        program: Int,
        values: FloatArray,
        offset: Int
    ) {
        GLES30.glVertexAttrib1fv(this.handle(program), values, offset)
    }

    fun glVertexAttrib1fv(
        program: Int,
        values: FloatBuffer
    ) {
        GLES30.glVertexAttrib1fv(this.handle(program), values)
    }

    fun glVertexAttrib2f(
        program: Int,
        x: Float,
        y: Float
    ) {
        GLES30.glVertexAttrib2f(this.handle(program), x, y)
    }

    fun glVertexAttrib2fv(
        program: Int,
        values: FloatArray,
        offset: Int
    ) {
        GLES30.glVertexAttrib2fv(this.handle(program), values, offset)
    }

    fun glVertexAttrib2fv(
        program: Int,
        values: FloatBuffer
    ) {
        GLES30.glVertexAttrib2fv(this.handle(program), values)
    }

    fun glVertexAttrib3f(
        program: Int,
        x: Float,
        y: Float,
        z: Float
    ) {
        GLES30.glVertexAttrib3f(this.handle(program), x, y, z)
    }

    fun glVertexAttrib3fv(
        program: Int,
        values: FloatArray,
        offset: Int
    ) {
        GLES30.glVertexAttrib3fv(this.handle(program), values, offset)
    }

    fun glVertexAttrib3fv(
        program: Int,
        values: FloatBuffer
    ) {
        GLES30.glVertexAttrib3fv(this.handle(program), values)
    }

    fun glVertexAttrib4f(
        program: Int,
        x: Float,
        y: Float,
        z: Float,
        w: Float
    ) {
        GLES30.glVertexAttrib4f(this.handle(program), x, y, z, w)
    }

    fun glVertexAttrib4fv(
        program: Int,
        values: FloatArray,
        offset: Int
    ) {
        GLES30.glVertexAttrib4fv(this.handle(program), values, offset)
    }

    fun glVertexAttrib4fv(
        program: Int,
        values: FloatBuffer
    ) {
        GLES30.glVertexAttrib4fv(this.handle(program), values)
    }

    fun glVertexAttribPointer(
        program: Int,
        size: Int,
        type: Int,
        normalized: Boolean,
        stride: Int,
        offset: Int
    ) {
        GLES30.glVertexAttribPointer(this.handle(program), size, type, normalized, stride, offset)
    }

    fun glVertexAttribPointer(
        program: Int,
        size: Int,
        type: Int,
        normalized: Boolean,
        stride: Int,
        ptr: Buffer
    ) {
        GLES30.glVertexAttribPointer(this.handle(program), size, type, normalized, stride, ptr)
    }
}

class GLVaryingVariable(obj: GLObject) : GLVariable(obj) {
    override fun type(): GLType {
        return GLType.Varying
    }
}

abstract class GlCode {
    abstract fun create(): String
}

object DefaultVShader : GlCode() {

    val uniform_mvp_matrix_name: GLVariable = GLUniformVariable(GLClass.Mat4.create("uMVPMatrix"))
    val attribute_position_name: GLVariable = GLAttributeVariable(GLClass.Vec4.create("aPosition"))

    override fun create(): String {
        return """
        $uniform_mvp_matrix_name;
        $attribute_position_name;
        void main() {
            gl_Position = ${uniform_mvp_matrix_name.name()} * ${attribute_position_name.name()};
        }
        """.trimIndent()
    }
}

object DefaultFShader : GlCode() {

    val uniform_color_name: GLVariable = GLUniformVariable(GLClass.Vec4.create("uColor"))

    override fun create(): String {
        return """
            precision mediump float;
            $uniform_color_name ;
            void main() {
                gl_FragColor = ${uniform_color_name.name()};
            }
        """.trimIndent()
    }
}